home *** CD-ROM | disk | FTP | other *** search
/ CD Ware Multimedia 1995 May / cd Ware (Juegos) Epimundo.iso / DOS / C / CJDATES.ZIP / DATES.ASM < prev    next >
Encoding:
Assembly Source File  |  1991-07-21  |  14.8 KB  |  403 lines

  1.     %PAGESIZE 58,124
  2. ;*******************************************************************************
  3. ;
  4. ;                                 ZDAY and ZDATE
  5. ;
  6. ; These are routines for converting Gregorian dates to and from Day Numbers.
  7. ;
  8. ; The routines are called using the PASCAL calling sequence, and are FAR calls.
  9. ; All pointers are FAR pointers.
  10. ;
  11. ; The routines are written to run on an 8088 so as to be compatible with all
  12. ; machines based on this architecture, and use the Borland "PASCAL" calling
  13. ; sequence so as to be callable from programs compiled with both Borland's C
  14. ; and Pascal compilers.
  15. ;
  16. ; The Borland Turbo Assembler (V2.0 or better) is required to assemble this code.
  17. ;
  18. ;===============================================================================
  19. ;
  20. ; The C calling sequences are defined by these prototypes:
  21. ;
  22. ; unsigned long int far pascal ZDay( unsigned int Year, unsigned int Month,
  23. ;                                 unsigned int Day );
  24. ;
  25. ; int far pascal ZDate(unsigned long int DayNumber, unsigned int far *Year,
  26. ;                   unsigned int far *Month, unsigned int far *Day );
  27. ;
  28. ;-------------------------------------------------------------------------------
  29. ;
  30. ; The PASCAL prototypes (you will $L the object code from this assembly into
  31. ; a TPU) would be:
  32. ;
  33. ; function ZDay(Year, Month, Day : word) : longint;
  34. ;
  35. ; function ZDate(DayNumber : longint; var Year, Month, Day : word ) : boolean;
  36. ;
  37. ;-------------------------------------------------------------------------------
  38. ;
  39. ; ZDay returns a 32-bit unsigned integer representing the Day Number calculated
  40. ; from the supplied Gregorian date.  Although the Pascal call defines it as a
  41. ; longint, you will not have to worry about getting a negative number back.
  42. ;
  43. ; If the resulting Day Number is zero, you have supplied a Gregorian date that
  44. ; is too early or too far in the future for the calculations to be performed
  45. ; correctly with this routine's methods.
  46. ;
  47. ; The Year, Month and Day parameters are all unsigned 16-bit integers,    Year
  48. ; must be the FULL YEAR.  1902 must be supplied as 1902, not 02.  It must be
  49. ; in the range 1 to 25599 or zero will be returned.  Month and Day must be in
  50. ; the range 0-65535.  These restrictions should not be too restrictive for most
  51. ; purposes.
  52. ;
  53. ; Note that Day, Month and Year are all UNSIGNED.  When you are doing weird
  54. ; date calculations you must NEVER try to supply negative values for these.
  55. ; This is also true of the any Day Number you give to ZDate!  Negative numbers
  56. ; look like large positive numbers and will give Wrong Results!
  57. ;
  58. ; The same parameters are used with ZDate, except no value is returned since it
  59. ; is a procedure (void function) rather than a function.  Just supply the Day
  60. ; Number, and ZDate fills in the Year, Month and Day for you.  The supplied Day
  61. ; Number should be greater than 121 and less than 23920640.  For Day Numbers
  62. ; outside this range, ZDate returns a Gregorian date of 0-0-0 and a return
  63. ; value of 0 (FALSE in both Pascal and C).  A good conversion returns 1 (TRUE
  64. ; in both C and Pascal).
  65. ;
  66. ; NOTE:  ZDay can convert certain dates into Day Numbers that are outside the
  67. ; range that ZDate can convert back.  These dates, however, involve years
  68. ; that are either zero or very, very large, and are well outside the range of
  69. ; dates that we are likely to be interested in.
  70. ;
  71. ; A call to ZDay uses 14 bytes of space on the stack; a call to ZDate uses 26.
  72. ;
  73. ;===============================================================================
  74. ;
  75. ;                WHAT'S IT ALL FOR, ANYWAY?
  76. ;
  77. ; All dates, valid or not, convert to Day Numbers of some kind.  All Day
  78. ; Numbers convert to valid Gregorian dates.  So if you convert a Gregorian
  79. ; date to a Day Number and back, if the resulting Gregorian date doesn't match
  80. ; the original, the original was invalid.
  81. ;
  82. ; The Day Number is related to the Julian Day Number, but is not the same.
  83. ; It is valid only for the years since the Gregorian calendar was introduced.
  84. ;
  85. ; You can use the Day Numbers of two dates to find the number of days between
  86. ; them.
  87. ;
  88. ; The remainder resulting when the Day Number is divided by 7 is the day of the
  89. ; week, with 0 (Sunday) thru 6 (Saturday).
  90. ;
  91. ; To find the last day of a month, begin with the Gregorian date.  Add 1 to
  92. ; the month (even to December), set the Day to 0, convert to a Day Number, then
  93. ; back to Gregorian.
  94. ;
  95. ; To find the Julian day of the year (different from the Julian Day Number!),
  96. ; convert the given Gregorian date to a Day Number.  Then subtract from this
  97. ; the Day Number of January 0 (NOT 1!) of the same year.
  98. ;
  99. ; Convert a Julian Date to Gregorian by taking the Day Number of of January
  100. ; ZERO of the year.  Add the Julian day of the year to this, then convert
  101. ; to a Gregorian date.
  102. ;
  103. ;*******************************************************************************
  104. ;                           (c) Copyright 1991 Crazy Jack
  105. ;                                 All Rights Reserved
  106.     %NEWPAGE
  107.     IDEAL
  108.     MODEL    LARGE
  109.     CODESEG
  110. ;
  111. ; The simplest routine converts the Gregorian date to a Day Number:
  112. ;
  113.     PROC    PASCAL ZDAY FAR Year:WORD, Month:WORD, Day:WORD
  114.     PUBLIC    ZDAY
  115.     MOV    CX, [Year]        ;Get Year.
  116.     MOV    BX, [Month]        ;Get Month.
  117.     CMP    BX, 14            ;Month greater than 14 will give
  118.     JA    FIXMNTH         ;incorrect results.
  119. MNTHOK:
  120.     CMP    BL, 2            ;Is Month January or February?
  121.     JA    NOADJ            ;Jump if not,
  122.  
  123.     OR    CX, CX            ;else be sure year isn't zero (we're
  124.     JZ    DATEBAD         ;in trouble if we decrement zero),
  125.     DEC    CX            ;then shift calculations to joint
  126.     ADD    BX, 12            ;between February and March.
  127. NOADJ:
  128.     CMP    CH, 100         ;Any year too big to be divided by 100
  129.     JAE    DATEBAD         ;16-by-8-bit will cause divide overflow.
  130.     INC    BX            ;Need a little adjustment here---.
  131.  
  132.     MOV    AX, 43857        ;Calculate number of days due to months:
  133.     MUL    BX            ;First multiply by 306001, more than 16
  134.     SHL    BX, 1            ;bits worth.  The upper word is 4.  We
  135.     SHL    BX, 1            ;use shifts for speed.    Earlier test
  136.     ADD    DX, BX            ;against 2141 ensures no overflow and
  137.     MOV    BX, 10000        ;that we can divide by 10000.
  138.     DIV    BX            ;This gives INT(month * 30.6001).
  139.     MOV    BX, AX            ;Save the result (days due to months).
  140.  
  141.     MOV    AX, 365         ;Now for days due to years:
  142.     MUL    CX            ;Gives days in normal years.
  143.     ADD    AX, BX            ;Add in days due to months.
  144.     ADC    DX, 0            ;(There will be no carry from this!)
  145.     PUSH    AX            ;We need the AX for another divide.
  146.  
  147.     SHR    CX, 1            ;Find number of extra days due to
  148.     SHR    CX, 1            ;leap years (add a year for every 4).
  149.     MOV    AX, CX            ;Remove days for 400-year
  150.     MOV    BL, 25            ;Gregorian rule:
  151.     DIV    BL            ;First remove leap year day for each
  152.     XOR    AH, AH            ;century---
  153.     SUB    CX, AX            ;Result will always be positive.
  154.     SHR    AL, 1            ;Then add back a leap year day for
  155.     SHR    AL, 1            ;each 400 years.
  156.     ADD    AX, CX            ;No carry will occur.  Why?
  157.  
  158.     POP    BX
  159.     ADD    AX, BX            ;Add leap year days to days due to
  160.     ADC    DX, 0            ;month and year.
  161.     ADD    AX, [Day]        ;Fold in day of the month.
  162.     ADC    DX, 0
  163.     SUB    AX, 1            ;Finally, adjust so remainder from
  164.     SBB    DX, 0            ;divide by 7 gives day of week.
  165.                     ;Back to caller with Day Number
  166. GONE:                    ;Day Number in DX:AX.
  167.     RET
  168. ;
  169. FIXMNTH:                ;Sigh.    Month is too big to give valid
  170.     MOV    AX, BX            ;results, so we must reduce it.  We put
  171.     XOR    DX, DX            ;this here since it won't happen often
  172.     MOV    BX, 12            ;and we don't want to slow the main line.
  173.     DIV    BX            ;We convert it to years and months.
  174.     MOV    BX, DX            ;Remainder becomes new month.
  175.     ADD    CX, AX            ;Quotient is years.  Add to given year.
  176.     JNC    MNTHOK            ;Back to conversion if still in range.
  177. ;
  178. DATEBAD:
  179.     XOR    AX, AX            ;If something's wrong, we clear the
  180.     MOV    DX, AX            ;Day Number in DX:AX to zero
  181.     JMP    GONE            ;and clear out.
  182. ;
  183.     ENDP    ZDAY
  184.     %NEWPAGE
  185. ;
  186. ; Converting a Day Number back to a Gregorian date is more complicated:
  187. ;
  188.     PROC    PASCAL ZDATE FAR DayNumber:WORD:2, Year:FAR PTR WORD, Month:FAR PTR WORD, Day:FAR PTR WORD
  189.     PUBLIC    ZDATE
  190.     PUSH    SI
  191.     PUSH    DI
  192.     MOV    DX, [DayNumber+2]    ;Get Day Number from caller.
  193.     CMP    DX, 365         ;Bigger than this and we can't
  194.     JAE    RELAY2            ;extract the year!
  195.     MOV    AX, [DayNumber]     ;Okay, get the rest of the Day Number.
  196.  
  197.     MOV    SI, AX            ;We save a copy of it.
  198.     MOV    DI, DX
  199.     SUB    AX, 121         ;First we back out the 400-year
  200.     SBB    DX, 0            ;Gregorian rule.
  201.     JC    RELAY1            ;Day number must be greater than 120.
  202.     MOV    BX, 48699        ;There are 146097 days in 400 years.
  203.     DIV    BX            ;146097 = 48699 * 3.  We divide in
  204.     MOV    CX, DX            ;two steps, first by 48699, then by 3.
  205.     XOR    DX, DX
  206.     MOV    BX, 3
  207.     DIV    BX            ;The resulting quotient 1/3 of the leap
  208.     ADD    SI, AX            ;year days removed by the 4000-year rule.
  209.     ADC    DI, 0            ;We add them back 3 times, which is
  210.     ADD    SI, AX            ;quicker than multiplying by 3.
  211.     ADC    DI, 0
  212.     INC    AX            ;Here's the quickest place to add back
  213.     ADD    SI, AX            ;the 1 we subtracted in ZDay to aid in
  214.     ADC    DI, 0            ;finding the day of the week.
  215.     MOV    AX, 48699        ;We now finish calculating the remainder
  216.     MUL    DX            ;from the two divisions which gives us
  217.     ADD    AX, CX            ;the number of days into the current 400
  218.     ADC    DX, 0            ;years.
  219.     MOV    CX, DX            ;Is there a remainder?
  220.     OR    CX, AX
  221.     JZ    GREGOUT         ;If not, we save some calculation time.
  222.  
  223.     SUB    AX, 1            ;Now we calculate the number of leap
  224.     SBB    DX, 0            ;years dropped so far THIS 400 years---
  225.     MOV    CX, 36524        ;(Number of days in 3 out of 4
  226.     DIV    CX            ;centuries.)
  227.     ADD    SI, AX            ;---and add THAT back in.
  228.     ADC    DI, 0
  229. GREGOUT:
  230.     MOV    AX, DI            ;We now the divide adjusted Day Number
  231.     MOV    BX, 100         ;by 365.25 to get the year.  The
  232.     MUL    BX            ;remainder will be the day of the year.
  233.     MOV    CX, AX
  234.     MOV    AX, SI
  235.     MUL    BX
  236.     ADD    DX, CX
  237.     SUB    AX, 12210
  238.     SBB    DX, 0
  239. RELAY1: JC    DAYBAD            ;Don't sweat the speed loss on errors.
  240.     MOV    CX, 36525
  241.     CMP    DX, CX            ;Be sure we can divide adjusted value.
  242. RELAY2: JAE    DAYBAD
  243.     DIV    CX
  244.     MOV    DI, AX            ;Year (or year-1) now in the DI.
  245.  
  246.     MOV    AX, 1461        ;Note that this gives us a number of
  247.     MUL    DI            ;days beyond the calculated year, which
  248.     RCR    DX, 1            ;is NOT the remainder from the previous
  249.     RCR    AX, 1            ;divide.
  250.     RCR    DX, 1            ;1461/4 = 365.25, our multiplier.
  251.     RCR    AX, 1            ;Since the difference can't exceed
  252.     SUB    SI, AX            ;487 we only subtract low-order words.
  253.  
  254.     MOV    AX, 10000        ;We must now divide the days beyond the
  255.     MUL    SI            ;calculated year by 30.6001 to get the
  256.     MOV    BX, 9871        ;unadjusted month.  To do this we multi-
  257.     DIV    BX            ;ply by 10000, then divide by 306001,
  258.     MOV    CX, 31            ;which is more than 16 bits long.  Since
  259.     DIV    CL            ;306001 = 31*9871, we divide in 2 steps.
  260.     MOV    CL, AL            ;Got the unadjusted month now in the BX.
  261.  
  262.     XCHG    DX, BX            ;We must now finish getting the
  263.     MOV    AL, AH            ;remainder which is 10000 times the
  264.     MOV    AH, CH            ;day of the month.
  265.     MUL    DX
  266.     ADD    AX, BX
  267.     ADC    DX, 0
  268.     MOV    BX, 10000        ;Divide out the 10000 and the AX contains
  269.     DIV    BX            ;one less than the day of the month>
  270.     INC    AX
  271.  
  272.     DEC    CL            ;Adjust start of year back to between
  273.     CMP    CL, 12            ;December and January.    By now the month
  274.     JBE    MNTHRDY         ;is less than 16 and the day is in the
  275.     SUB    CL, 12            ;range 1-31.
  276.     INC    DI
  277. MNTHRDY:
  278.     MOV    DX, DS            ;Begin storing results for caller.
  279.     LDS    BX, [Day]
  280.     MOV    [BX], AX
  281.     MOV    AL, 1            ;Set Pascal/C TRUE for caller (AH=0).
  282. OUTAHERE:
  283.     LDS    BX, [Month]
  284.     MOV    [BX], CX
  285.     LDS    BX, [Year]
  286.     MOV    [BX], DI
  287. ;
  288.     MOV    DS, DX            ;Restore caller and return.
  289.     POP    DI
  290.     POP    SI
  291.     RET
  292. ;
  293. DAYBAD:
  294.     MOV    DX, DS
  295.     XOR    AX, AX            ;Supplies zero to store and to return.
  296.     LDS    BX, [Day]        ;Return zeroes for all output values.
  297.     MOV    [BX], AX
  298.     MOV    CX, AX
  299.     MOV    DI, AX
  300.     JMP    OUTAHERE        ;Go return to caller.
  301. ;
  302.     ENDP    ZDATE                  
  303. ;
  304.     ENDS
  305.     %NEWPAGE
  306. ;*******************************************************************************
  307. ;
  308. ;                 About the Claculations
  309. ;
  310. ; There are a number of descriptions of Day Number routines floating around.  
  311. ; The ones I use came from a routine I saw for the HP-65 programmable pocket
  312. ; calculator to find the Julian Day Number.  To it I added adjustments for the
  313. ; 400-day Gregorian rule.  We don't need it for the coming turn of the century
  314. ; since 200 divides by 400 without  a remainder and is, thus, a leap year, but
  315. ; programmers are picky by nature, so -- what the heck.
  316. ;
  317. ; To calculate the Day Number from a Gregorian date we first adjust the year and
  318. ; month so the month values run from 4 to 15, with March being 4 and February 
  319. ; being 15:
  320. ;
  321. ;    if Month < 3
  322. ;    then
  323. ;           Add 13 to Month
  324. ;        Subtract 1 from Year
  325. ;    otherwise
  326. ;        Add 1 to Month.
  327. ;
  328. ; Now comes the meat of the calculation:
  329. ;
  330. ;    Day Number =   Day of the month
  331. ;             + The Integer Part of (Month * 30.6001)
  332. ;             + The Integer Part of (Year * 365.25)
  333. ;             - The Integer Part of (3/4 of
  334. ;                        The Integer Part of (Year / 400)
  335. ;                       ).
  336. ;
  337. ; That last mess accounts for the Gregorian 400-year rule.  Now if we divide
  338. ; this by 7 (to find the day of the week) we get 0 for Saturday.  For a
  339. ; variety of reasons, mostly related to common practice, I decided to adjust
  340. ; this further by subtracting 1 from it so Sunday becomes 0.
  341. ;
  342. ; Converting back is not so easy.
  343. ;
  344. ; First we back out the Gregorian 400-year rule:
  345. ;
  346. ;    Divide Day Number less 121 by 146097:
  347. ;           Q = the quotient
  348. ;           R = the remainder.
  349. ;
  350. ;    if R is not 0
  351. ;    then
  352. ;        Add the quotient from ( (R -1) / 36524 ) to Q.
  353. ;
  354. ;    N = Day Number plus 1 plus Q.
  355. ;
  356. ; Note that 145097 is the number of days in 400 years with the 400-year rule 
  357. ; applied, and 36524 is the number of days in a century in which the century 
  358. ; year is NOT a leap year.  36525 is the number of days in a century where the 
  359. ; century year IS a leap year.    Once the 400-year rule is backed out, we have
  360. ; ALL centuries containing 36525 days, which simplifies our calculations.
  361. ;
  362. ; Now we can extract the Gregorian date from the adjusted Day Number "N".
  363. ; First we get the tentative year:
  364. ;
  365. ;    Year = The Integer Part of ( (N - 122.1) / 365.25 ).
  366. ;
  367. ; --and we remove the year's worth of days from the adjusted Day Number "N":
  368. ;
  369. ;    N = N - The Integer Part of (Year * 365.25).
  370. ;
  371. ; From this we extract the tentative month:
  372. ;
  373. ;    Month = The Integer Part of (N / 30.6001).
  374. ;
  375. ; The current day is what is left over when we remove the days due to the
  376. ; months:
  377. ;
  378. ;    Day = N - The Integer Ppart of (Month * 30.6001).
  379. ;
  380. ; Finally we adjust the Year and the Month:
  381. ;
  382. ;       if Month > 13
  383. ;       then
  384. ;           Subtract 13 from Month
  385. ;           Add 1 to the Year
  386. ;       else
  387. ;           Subtract 1 from the Month.
  388. ;
  389. ; --and the deed is done.
  390. ;
  391. ; Since it is my intention that this set of routines be usable on all PC com-
  392. ; patible systems, we can't assume the availability of a numeric coprocessor or 
  393. ; 32-bit arithmetic, so everything is done in 16-bit integer arithmetic using 
  394. ; the CPU registers and 8086 instructions.  In some places it makes the code a
  395. ; little awkward, but it gives its best performance on the lowliest of PCs
  396. ; where it's needed the most.
  397. ;
  398. ; I chose the Pascal calling sequence so that I could use the code with both C 
  399. ; and Pascal programs.    If you don't mind shoving stuff on the stack and the
  400. ; other fooling around, you can use the routines with assembly code as well.
  401. ;
  402.     END
  403.